﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace GmBoxML
{
    public partial class Form1 : Form
    {
        private const string FP_ART_INFO = "ArtInfos.csv";
        private const char FG_VALUE = '┏';
        private const string ID_CATCH_HEAD_STRING = "id%3D";

        private const string DC_SongName = "SongName",
                             DC_SongId = "SonyId",
                             DC_SongNum = "SonyName",
                             DC_ArtistName = "ArtName",
                             DC_AlbumName = "AlbumName",
                             DC_AlbumMemo = "AlbumMemo",
                             DC_ArtistId = "ArtId",
                             DC_AlbumId = "AlbumId",
                             FN_InitArtSetting = "InitArtSetting.txt";

        private const string FG_InitSplit = "-|-";
        private const string FN_SaveDirPath = "SaveDirPath.txt", FN_SplitTime = "SplitTimeEachDownFile.txt";

        private DataView m_CurrAlbums;
        private string m_CurrAlbumsName;
        private string m_CurrArtistName;
        private DataView m_CurrArtists;
        private string m_CurrSongPageUrl = "";

        public Form1()
        {
            InitializeComponent();
        }

        private string GetSettingTxtVal(string filePath)
        {
            string reVal = "";
            if (File.Exists(filePath))
            {
                var sr = new StreamReader(filePath);
                try
                {
                    string pathVal = sr.ReadToEnd();
                    if (pathVal.EndsWith("\r\n")) pathVal = pathVal.Remove(pathVal.Length - ("\r\n").Length);
                    if (pathVal.Length > 0) reVal = pathVal;
                }
                finally
                {
                    sr.Close();
                }
            }
            return reVal;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            string path = "";
            path = GetSettingTxtVal(FN_SaveDirPath);
            if (path.Length > 0 && !Directory.Exists(path)) Directory.CreateDirectory(path);
            string splitTime = GetSettingTxtVal(FN_SplitTime);
            if (!string.IsNullOrEmpty(splitTime)) txtSleepTime.Text = splitTime;
            if (string.IsNullOrEmpty(path))
            {
                path = AppDomain.CurrentDomain.BaseDirectory + "GmDownloadFiles";
                if (Directory.Exists(path)) Directory.CreateDirectory(path);
            }
            ddlThreadAmount.SelectedIndex = 0;
            txtSaveFolderPath.Text = path;
        }

        public string DownSong(string sourceUrl, string filePath)
        {
            return DownloadFile(sourceUrl, filePath);
        }


        public string DownloadFile(string url, string filePath)
        {
            string reMsg = "";
            if (File.Exists(filePath)) return reMsg;
            Stream st = null, so = null;
            WebResponse myrp = null;
            StreamWriter swErr = null;
            var Myrq = (HttpWebRequest) WebRequest.Create(url);
            try
            {
                myrp = Myrq.GetResponse();
                if (myrp.ContentLength != 0)
                {
                    // MessageBox.Show("文件存在");
                    st = myrp.GetResponseStream();
                    so = new FileStream(filePath,
                                        FileMode.Create);
                    long totalDownloadedByte = 0;
                    var by = new byte[1024];
                    int osize = st.Read(by, 0, by.Length);
                    while (osize > 0)
                    {
                        totalDownloadedByte = osize + totalDownloadedByte;
                        so.Write(by, 0, osize);
                        osize = st.Read(by, 0, by.Length);
                    }
                }
            }
            catch (Exception ex)
            {
                reMsg = url + "\r\n" + ex.Message;
                swErr = new StreamWriter("ErrDownUrl_[" + DateTime.Now.ToString("yyyyMMddhhmmss") + "].txt", false,
                                         Encoding.UTF8);
                swErr.WriteLine(m_CurrSongPageUrl + "\r\n\r\n" + url + "\r\n\r\n" + filePath);
            }
            finally
            {
                if (so != null) so.Close();
                if (st != null) st.Close();
                if (myrp != null) myrp.Close();
                if (swErr != null) swErr.Close();
            }
            Thread.Sleep(Convert.ToInt32(txtSleepTime.Text));
            return reMsg;
        }


        public HtmlDocument GetHtmlDocmentByUrl(string urlStr, bool isReplaceInHis)
        {
            var myReq = (HttpWebRequest) WebRequest.Create(urlStr);
            var myResp = (HttpWebResponse) myReq.GetResponse();
            Stream rStream = myResp.GetResponseStream();
            if (rStream == null) return null;
            var respStream = new StreamReader(rStream, Encoding.UTF8);
            string strhtml;
            try
            {
                strhtml = respStream.ReadToEnd();
            }
            finally
            {
                respStream.Dispose();
                respStream.Close();
                myResp.Close();
            }
            if (string.IsNullOrEmpty(strhtml)) return null;
            wb.Navigate("about:blank");
            wb.Document.OpenNew(isReplaceInHis);
            wb.Document.Write(strhtml);
            return wb.Document;
        }

        public HtmlDocument GetHtmlDocmentByUrl(string urlStr)
        {
            return GetHtmlDocmentByUrl(urlStr, false);
        }

        public HtmlDocument GetHtmlDocmentByUrl_2(string urlStr)
        {
            wb.Navigate(urlStr);
            while (wb.IsBusy) Thread.Sleep(200);
            if (wb.Document == null) return null;
            wb.Document.OpenNew(true);
            return wb.Document;
        }

        public string InitArtisInfos(Hashtable htUrls)
        {
            var sbReVal = new StringBuilder();

            var sb = new StringBuilder();
            foreach (string groupName in htUrls.Keys)
            {
                if (sbReVal.Length > 0)
                {
                    sbReVal.Append("\r\n");
                    Thread.Sleep(2000);
                }
                sbReVal.Append(groupName);
                string strUrl = htUrls[groupName].ToString();
                HtmlDocument doc = GetHtmlDocmentByUrl(strUrl);
                if (doc == null)
                {
                    sbReVal.Append("数据加载失败");
                    continue;
                }
                HtmlElementCollection el = doc.GetElementsByTagName("a");

                const string strStartsWith = "<a href=\"/music/url?q=/music/artist?id%3D";
                string startsWithLowerStr = strStartsWith.ToLower();
                foreach (HtmlElement e in el)
                {
                    string str = e.OuterHtml;
                    string strLower = str.ToLower();
                    if (strLower.StartsWith(startsWithLowerStr))
                    {
                        str = str.Remove(0, strStartsWith.Length);
                        int tempIndex = str.IndexOf('&');
                        if (tempIndex > -1)
                        {
                            string strArtId = str.Substring(0, tempIndex);
                            string strArtName = e.InnerText;
                            sb.Append(strArtId + FG_VALUE + strArtName + FG_VALUE + groupName + "\r\n");
                        }
                    }
                }
                sbReVal.Append("数据加载成功");
            }
            if (sb.Length > 0)
            {
                var sw = new StreamWriter(FP_ART_INFO, false, Encoding.UTF8);
                try
                {
                    sw.Write(sb);
                }
                finally
                {
                    sw.Dispose();
                    sw.Close();
                }
            }
            return sbReVal.ToString();
        }

        private void btnQuery_Click(object sender, EventArgs e)
        {
            string str = GetSettingTxtVal(FN_InitArtSetting);
            str = str.Replace("\r\n", FG_VALUE.ToString());
            string[] ss = str.Split(FG_VALUE);
            var initInfo = new Hashtable();
            foreach (string s in ss)
            {
                int iFg = s.IndexOf(FG_InitSplit);
                if (iFg < 0) continue;
                string key = s.Substring(0, iFg);
                string val = s.Substring(iFg + FG_InitSplit.Length);
                initInfo.Add(key, val);
            }
            string msg = InitArtisInfos(initInfo);
            MessageBox.Show(msg);
        }

        private void SetArtistsInfo(string filterString)
        {
            var dt = new DataTable("ArtInfos");
            dt.Columns.Add(DC_ArtistId, typeof (string));
            dt.Columns.Add(DC_ArtistName, typeof (string));
            dt.Columns.Add("GroupName", typeof (string));

            var sr = new StreamReader(FP_ART_INFO);
            try
            {
                while (sr.Peek() > 0)
                {
                    string rowContext = sr.ReadLine();
                    if (rowContext == null) continue;
                    string[] ss = rowContext.Split(FG_VALUE);
                    if (ss.Length > 2)
                    {
                        dt.Rows.Add(dt.NewRow().ItemArray = new[] {ss[0], ss[1], ss[2]});
                    }
                }
            }
            finally
            {
                sr.Dispose();
                sr.Close();
            }
            DataView dv = dt.DefaultView;
            dv.RowFilter = filterString;
            m_CurrArtists = dv;
        }

        private void SetDataViewToList(DataView dv)
        {
            if (dv == null) return;
            dvList.DataSource = dv;
            dvList.Show();
            cmsForDv.Items[0].Enabled = true;
            cmsForDv.Items[2].Enabled = cmsForDv.Items[1].Enabled = false;
        }

        private void SetDataView(string filterString)
        {
            SetArtistsInfo(filterString);
            SetDataViewToList(m_CurrArtists);
        }

        private void btnFilter_Click(object sender, EventArgs e)
        {
            if (!File.Exists(FP_ART_INFO))
            {
                MessageBox.Show("请先加载歌手数据");
                return;
            }
            string strFilter = txtArtist.Text.Length > 0 ? DC_ArtistName + " like '%" + txtArtist.Text + "%'" : "";
            SetDataView(strFilter);
            if (!string.IsNullOrEmpty(txtSleepTime.Text)) SetSettingTxtVal(FN_SplitTime, txtSleepTime.Text);
        }

        private string GetCurrCellVal(string colName)
        {
            if (dvList.CurrentRow == null) return "";
            DataGridViewRow row = dvList.CurrentRow;
            string strId = Convert.ToString(row.Cells[colName].Value);
            return strId;
        }

        private StringCollection GetSelectCellVal(string colName)
        {
            var reVal = new StringCollection();
            if (dvList.CurrentRow == null) return reVal;
            var rows = dvList.SelectedRows;
            foreach(DataGridViewRow row  in rows)
            {
                string strId = Convert.ToString(row.Cells[colName].Value);
                if (!string.IsNullOrEmpty(strId))
                    reVal.Add(strId);
            }
            
            return reVal;
        }

        private DataView GetAlbumsInfoByArtId(string artId)
        {
            HtmlDocument doc = GetHtmlDocmentByUrl("http://www.google.cn/music/artist?id=" + artId);
            HtmlElementCollection elAs = doc.GetElementsByTagName("a");
            var dt = new DataTable("ArtAlbumInfos");

            dt.Columns.Add(DC_AlbumId, typeof (string));
            dt.Columns.Add(DC_AlbumName, typeof (string));
            dt.Columns.Add(DC_AlbumMemo, typeof (string));
            dt.Columns.Add(DC_ArtistId, typeof (string));

            foreach (HtmlElement ela in elAs)
            {
                if (!string.IsNullOrEmpty(ela.InnerText))
                {
                    int tempStartIndex = ela.OuterHtml.ToLower().IndexOf(ID_CATCH_HEAD_STRING.ToLower());
                    if (tempStartIndex == -1) continue;
                    tempStartIndex += ID_CATCH_HEAD_STRING.Length;
                    int tempEndIndex = ela.OuterHtml.IndexOf("&", tempStartIndex);
                    if (tempEndIndex == -1) continue;
                    if (tempEndIndex > tempStartIndex)
                    {
                        if (ela.Parent.Parent != null && ela.Parent.Parent.TagName.ToLower() == "tr" &&
                            ela.Parent.Parent.NextSibling != null)
                        {
                            string strAlbumId = ela.OuterHtml.Substring(tempStartIndex, tempEndIndex - tempStartIndex);
                            string strAlbumName = ela.InnerText;
                            string strAlbumMemo = ela.Parent.Parent.NextSibling.InnerText;
                            dt.Rows.Add(dt.NewRow().ItemArray = new[] {strAlbumId, strAlbumName, strAlbumMemo, artId});
                        }
                    }
                }
            }
            m_CurrAlbums = dt.DefaultView;
            return dt.DefaultView;
        }

        private void iAlbums_Click(object sender, EventArgs e)
        {
            string artId = GetCurrCellVal(DC_ArtistId);
            m_CurrArtistName = GetCurrCellVal(DC_ArtistName);
            dvList.DataSource = GetAlbumsInfoByArtId(artId);
            dvList.Show();
            cmsForDv.Items[2].Enabled = cmsForDv.Items[1].Enabled = true;
            cmsForDv.Items[0].Enabled = false;
        }

        private string GetNextNodeInnerText(ref HtmlElement curr, string classNameFilter)
        {
            string reVal = "";
            HtmlElement tempTd = curr.NextSibling;
            if (tempTd != null && tempTd.GetAttribute("ClassName") == classNameFilter)
            {
                reVal = tempTd.InnerText;
                curr = tempTd;
            }
            return reVal;
        }

        private DataView GetSonysInfoByAlbumId(string strAlbumId)
        {
            HtmlDocument doc = GetHtmlDocmentByUrl("http://www.google.cn/music/album?id=" + strAlbumId);
            HtmlElementCollection elAs = doc.GetElementsByTagName("input");
            var dt = new DataTable("AlbumSongInfos");
            dt.Columns.Add(DC_SongId, typeof (string));
            dt.Columns.Add(DC_SongNum, typeof (string));
            dt.Columns.Add(DC_SongName, typeof (string));
            dt.Columns.Add(DC_ArtistName, typeof (string));
            dt.Columns.Add(DC_AlbumName, typeof (string));
            foreach (HtmlElement ela in elAs)
            {
                if (ela.Name == "item_toggle")
                {
                    string strSonyId = ela.GetAttribute("value");
                    if (ela.Parent == null) continue;
                    HtmlElement currNode = ela.Parent;
                    string strSonyNum = GetNextNodeInnerText(ref currNode, "number BottomBorder");
                    string strSonyName = GetNextNodeInnerText(ref currNode, "Title BottomBorder");
                    string strArtName = GetNextNodeInnerText(ref currNode, "Artist BottomBorder");
                    DataRow[] rs = m_CurrAlbums.Table.Select(DC_AlbumId + "='" + strAlbumId + "'");
                    if (rs.Length != 1)
                        continue;
                    string strAlbumName = Convert.ToString(rs[0][DC_AlbumName]);
                    dt.Rows.Add(
                        dt.NewRow().ItemArray = new[] {strSonyId, strSonyNum, strSonyName, strArtName, strAlbumName});
                }
            }
            return dt.DefaultView;
        }

        private string GetSonyDownUrl(string songId)
        {
            string trgUrl = "";
            string songPageUrl = "http://www.google.cn/music/top100/musicdownload?id=" + songId;
            HtmlDocument doc = GetHtmlDocmentByUrl(songPageUrl, false);
            HtmlElementCollection els = doc.GetElementsByTagName("div");
            var sb = new StringBuilder();
            foreach (HtmlElement ela in els)
            {
                if (ela.GetAttribute("ClassName").ToLower() == "download")
                {
                    if (ela.Children.Count > 2 && ela.Children[2].TagName.ToLower() == "a")
                    {
                        string herfVal = ela.Children[2].GetAttribute("href");
                        trgUrl = herfVal.Replace("about:/", "http://www.google.cn/");
                        m_CurrSongPageUrl = songPageUrl;
                    }
                }

                sb.Append(ela.OuterHtml);
                sb.Append("\r\n\r\n");
            }
            return trgUrl;
        }

        private void iSongs_Click(object sender, EventArgs e)
        {
            dvList.DataSource = GetSonysInfoByAlbumId(GetCurrCellVal(DC_AlbumId));
            dvList.Show();
            cmsForDv.Items[0].Enabled = cmsForDv.Items[2].Enabled = cmsForDv.Items[1].Enabled = false;
        }

        private void DownSong(object obj)
        {
            if(obj!=null && obj is string[] && ((string[])obj).Length==3)
            {
                var ss = obj as string[];
                var trgUrl = (ss[0]);
                var filePath = (ss[1]);
                var index = Convert.ToInt32(ss[2]);
                DownSong(trgUrl, filePath);
                m_UsedThreadIndexs.Add(index);
            }
        }
        
        List<int> m_UsedThreadIndexs = new List<int>();
        private int m_DownAmount = 0;

        private void iDownload_Click(object sender, EventArgs e)
        {
            var scAlbumNames = GetSelectCellVal(DC_AlbumName);
            var scAlbumIds = GetSelectCellVal(DC_AlbumId);
            var sb = new StringBuilder();
            var threadAmount = Convert.ToInt32(ddlThreadAmount.SelectedItem);
            var ths = new Thread[threadAmount];
            for(var i=0;i<ths.Length;i++)
                m_UsedThreadIndexs.Add(i);

            for(var i=0;i<scAlbumIds.Count;i++)
            {
                m_CurrAlbumsName = scAlbumNames[i];
                DataView dv = GetSonysInfoByAlbumId(scAlbumIds[i]);
                if (dv == null) return;
                DataTable dt = dv.Table;
                foreach (DataRow r in dt.Rows)
                {
                    m_DownAmount++;
                    while (threadAmount > 1 && m_UsedThreadIndexs.Count == 0)
                        Thread.Sleep(500);
                    if (m_DownAmount>1) Thread.Sleep(Convert.ToInt32(txtSleepTime.Text));
                    string songId = Convert.ToString(r[DC_SongId]);
                    string songNum = Convert.ToString(r[DC_SongNum]);
                    if (songNum.EndsWith(".")) songNum = songNum.Substring(0, songNum.Length - 1);
                    string songName = Convert.ToString(r[DC_SongName]);
                    string trgUrl = (GetSonyDownUrl(songId));
                    if (trgUrl.Length > 0)
                    {
                        string strPath = txtSaveFolderPath.Text;
                        string strFileName = m_CurrArtistName + "." + m_CurrAlbumsName + "." + songNum.PadLeft(2, '0') +
                                             " - " + songName + ".mp3";
                        strPath += "\\" + m_CurrArtistName;
                        if (!Directory.Exists(strPath)) Directory.CreateDirectory(strPath);
                        strPath += "\\" + m_CurrAlbumsName;
                        if (!Directory.Exists(strPath)) Directory.CreateDirectory(strPath);
                        strPath += "\\" + strFileName;
                        string errMsg = "";
                        if(threadAmount == 1)
                        {
                             errMsg = DownSong(trgUrl, strPath);
                        }
                        else
                        {
                            if (m_UsedThreadIndexs.Count > 0)
                            {
                                var index = m_UsedThreadIndexs[0];
                                m_UsedThreadIndexs.Remove(0);
                                var th = ths[index] ?? new Thread(DownSong);
                                th.Start(new[] { trgUrl, strPath, index.ToString() });
                            }
                        }
                        
                        
                        if (errMsg.Length > 0)
                        {
                            sb.Append(m_CurrSongPageUrl + "\r\n\r\n" + strFileName + "\r\n" + errMsg + "\r\n\r\n");
                            break;
                        }
                    }
                }
            }
            
            MessageBox.Show(sb.Length == 0 ? "专辑下载完成" : sb.ToString());
        }

        private void btnCurrAlbum_Click(object sender, EventArgs e)
        {
            SetDataViewToList(m_CurrAlbums);
            cmsForDv.Items[2].Enabled = cmsForDv.Items[1].Enabled = true;
            cmsForDv.Items[0].Enabled = false;
        }

        private void SetSettingTxtVal(string filePath, string val)
        {
            var sw = new StreamWriter(filePath, false, Encoding.UTF8);
            try
            {
                sw.WriteLine(val);
            }
            finally
            {
                sw.Close();
            }
        }

        private void btnSetFolder_Click(object sender, EventArgs e)
        {
            if (fbdSavePath.ShowDialog() == DialogResult.OK)
            {
                txtSaveFolderPath.Text = fbdSavePath.SelectedPath;
                SetSettingTxtVal(FN_SaveDirPath, txtSaveFolderPath.Text);
            }
        }
    }
}